# -*- coding: utf-8 -*-

import sqlalchemy as sq
import sqlalchemy.orm as orm
import sys

__all__ = [
'REL_ONE_TO_ONE', 'REL_ONE_TO_MANY', 'REL_MANY_TO_MANY',
'DbObjectAbstract', 'DbObject'
]

# Relation types definition
REL_ONE_TO_ONE = 0
REL_ONE_TO_MANY = 1
REL_MANY_TO_MANY = 2

def new_object(objt, prefix, connection, metadata, session, **kw):
	return obj(obj, prefix, connection, metadata, session, **kw)

class DbObjectAbstract(object):
	def __init__(self, prefix='', connection=None, metadata=None, session=None, **kw):
		self.prefix = prefix
		self.connection = connection or None
		self.metadata = metadata or (self.connection and sq.MetaData(bind=self.connection)) or None
		self.table = None
		self.m_prefix = getattr(sys.modules[self.__module__], 'm_prefix') or ''
		self.t_name = self.prefix + self.m_prefix + self.t_name
	
	def _load_table(self):
		if ( not self.t_name in self.metadata.tables.keys() ):
			self.table = sq.Table(self.t_name, self.metadata, *self._columns())
		else:
			self.table = self.metadata.tables[self.t_name]

	def new(self, obj, **kw):
		return obj(self.prefix, self.connection, self.metadata, **kw)
	
	def FK(self, obj, key, **kw):
		o = self.new(obj)
		o._load_table()
		return sq.ForeignKey(o.table.c[key], **kw)

	def map_many(self, obj_list):
		new_list = []
		while obj_list:
			o = self.new(getattr(sys.modules[self.__module__], obj_list.pop(0)))
			o.map()
			new_list.append(o)
		return new_list

	def _not_impl(self):
		raise NotImplementedError, "This is abstract class. You can't use it directly. Child classes HAVE\
			to define this method by themselves"

	_table = map = _columns = _not_impl

class DbObject(DbObjectAbstract):
	def __init__(self, prefix='', connection=None, metadata=None, session=None, **kw):
		DbObjectAbstract.__init__(self, prefix, connection, metadata, **kw)
		self.loaded = False
		self.relations = {REL_ONE_TO_ONE:[], REL_ONE_TO_MANY:[], REL_MANY_TO_MANY:[]}
		self.properties = None
		self._load_table()
		self._o = type(self.t_name+'_class', (), {})
		self._init()
		self.session = session
		self.create_session()

	#_init = DbObjectAbstract._not_impl
	def _init(self): pass
		
	def map(self):
		if(not hasattr(self._o, '__do_mapped')):
			if(not self.table):
				self._load_table()
			self._o.__do_mapped = True
			self._o._do_factory = self
			self._map()
	
	def _map(self):
		props = {}
		relations = []
		for (relation, kw) in self.relations[REL_ONE_TO_ONE]:
			relation_i = self.new(relation)
			props[relation.t_name] = orm.relation(relation_i._o, backref=self.__class__.t_name, **kw)
			relations.append(relation_i)
		for (relation, kw) in self.relations[REL_ONE_TO_MANY]:
			relation_i = self.new(relation)
			props[relation.t_name] = orm.relation(relation_i._o, **kw)
			relations.append(relation_i)
		for ((relation, secondary), kw) in self.relations[REL_MANY_TO_MANY]:
			relation_i = self.new(relation)
			secondary_i = self.new(secondary)
			secondary_i._load_table()
			secondary_table = secondary_i.table
			props[relation.t_name] = orm.relation(relation_i._o, secondary=secondary_table, **kw)
			relations.append(relation_i)
		if(self.properties):
			props.update(self.properties)
		for rel in relations:
			if(not hasattr(rel._o, '__do_mapped')):
				rel.map()
		if(props):
			orm.mapper(self._o, self.table, properties=props)
		else:
			orm.mapper(self._o, self.table)

	def save(self):
		self.session.flush()

	def reset(self, flush=True):
		if(self.session):
			if(flush): self.session.flush()
			self.session.close()

	def __call__(self):
		self.create_session()
		return self._o()

	def create_session(self):
		if(not self.session): 
			if(hasattr(orm, 'sessionmaker')):
				self.session = orm.sessionmaker(bind=self.connection)
				self.session = self.session()
			else:
				create_session = orm.create_session
				self.session = create_session(bind=self.connection)	
	
	def qry(self):
		self.create_session()
		return self.session.query(self._o)
	
	def _add_relation(self, type, relation, **kw):
		self.relations[type].append( (relation, kw) )
